using System.Linq.Expressions;
using System;
using Polymod.TemplateBuilders;

namespace Polymod.Language
{
    internal class TemplateBuilderLanguage<Builder, T> : ITemplateBuilderLanguage<T>
    where Builder : IFormulaTemplateBuilder, IRegisterInvoker, IAffected
    {
        Builder _builder;

        public TemplateBuilderLanguage(Builder builder)
        {
            if (builder == null) throw new System.ArgumentNullException("builder");

            _builder = builder;
        }
        //TODO: Formula<,> should also have a CreateFormula<R>... so you can chain calls.

        public Formula<T, R> CreateFormula<R>(string name, Expression<Func<T, R>> expression)
        {
            var visitor = new FormulaVisitor();
            var formulaExpression = (Expression<Func<IModel, object>>)visitor.Visit(expression);
            _builder.SetInvoker(typeof(T), name, new FormulaInvoker(formulaExpression.Compile(), name, typeof(R)));

            //TODO: affectedProperties MUST include properties affected by formula (i.e. if formula contains another, properties in the inner formula needs to be taken into account). Possibly merge the two expression visitors in a single class.
            var affectedProperties = new AffectedPropertiesVisitor();
            affectedProperties.Visit(expression);
            _builder.Affects(typeof(T), name, affectedProperties.AffectedProperties);

            var result = new Formula<T, R>(name, expression);
            return result;
        }


        public void WhenChanged(string name, Action<IModel<T>> action)
        {
            var inner = _builder.GetInvoker(typeof(T), name);
            var onSet = new OnSetInvoker(inner, action);
            _builder.SetInvoker(typeof(T), name, onSet);
        }

        public void CreateProperty<R>(string name)
        {
            _builder.SetInvoker(typeof(T), name, new NewPropertyInvoker(name, typeof(R)));
        }
    }
}
